/*

This file is part of Rooms3D.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
Rooms3D is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
Rooms3D is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Rooms3D; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/
#include "StdAfx.h"
#include "ThumbnailExtractor.h"

#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Gdi32.lib") 


CThumbnailExtractor::CThumbnailExtractor(void)
{
	pidlFileURL = NULL;
	pidlWorkingDirectory = NULL;

	psfDesktop = NULL;
	psfWorkingDirectory = NULL;
	psiFileItem = NULL;
	
	peiIExtractImage = NULL;
	peiIExtractIcon = NULL;
	ptpThumbnailProvider = NULL;

	hrCritical = S_OK;

	desktopWindow = NULL; 
	hdc = NULL;
	thumbnailBitmap = NULL;
	hInstance = NULL;
	ANDmaskIcon = NULL;
	XORmaskIcon = NULL;
	thumbnailIcon = NULL;
	spStream = NULL;

	
	/*
		Obtaining temporary folder in the system
	*/
	DWORD tempFolderPathLength = ::GetTempPath(0, _T(""));  
	if(tempFolderPathLength == 0)
		hrCritical = -1;
	WCHAR *m_wsBuffer = new WCHAR[tempFolderPathLength + 1];
	tempFolderPathLength = ::GetTempPath(tempFolderPathLength + 1, m_wsBuffer); 
	m_wsTemporaryFolderPath += m_wsBuffer;
	delete m_wsBuffer;


	CoInitialize(NULL);


	hrCritical = SHGetMalloc(&m_pMalloc);

	if(FAILED(hrCritical))
		m_pMalloc = NULL;
}

CThumbnailExtractor::~CThumbnailExtractor()
{
   // free allocated structures
	//printf("Starting ~CThumbnailExtractor()\r\n");

	m_wsTemporaryFolderPath.clear();
	
	//printf("After m_wsTemporaryFolderPath.clear();  \r\n");

	//printf(" m_pMalloc = %p; pidlFileURL = %p \r\n", m_pMalloc, pidlFileURL);
	if(m_pMalloc != NULL && pidlFileURL != NULL) 
		m_pMalloc->Free(pidlFileURL);

	//printf(" m_pMalloc = %p; pidlWorkingDirectory = %p \r\n", m_pMalloc, pidlWorkingDirectory);

	if(m_pMalloc != NULL && pidlWorkingDirectory != NULL) 
	   m_pMalloc->Free(pidlWorkingDirectory);

	//printf(" m_pMalloc = %p;\r\n", m_pMalloc);
	if(m_pMalloc != NULL) 
		m_pMalloc.Release();


	//printf(" psfDesktop = %p \r\n", psfDesktop);
	if(psfDesktop != NULL) 
	   psfDesktop->Release();
	
	//printf(" psfWorkingDirectory = %p \r\n", psfWorkingDirectory);
	if(psfWorkingDirectory != NULL) 
	   psfWorkingDirectory->Release();

	//printf(" psiFileItem = %p \r\n", psiFileItem);
	if(psiFileItem != NULL) 
	   psiFileItem->Release();

	//printf(" peiIExtractImage = %p \r\n", peiIExtractImage);
	if(peiIExtractImage != NULL) 
	   peiIExtractImage->Release();

	//printf(" peiIExtractIcon = %p \r\n", peiIExtractIcon);
	if(peiIExtractIcon != NULL) 
	   peiIExtractIcon->Release();

	//printf(" ptpThumbnailProvider = %p \r\n", ptpThumbnailProvider);
	if(ptpThumbnailProvider != NULL) 
	   ptpThumbnailProvider->Release();

	//printf(" thumbnailBitmap = %p \r\n", thumbnailBitmap);
	if(thumbnailBitmap != NULL)
		DeleteObject(thumbnailBitmap);

	//printf(" thumbnailIcon = %p \r\n", thumbnailIcon);
	if(thumbnailIcon != NULL)
		DestroyIcon(thumbnailIcon);

	//printf(" hdc = %p \r\n", thumbnailIcon);
	if(hdc != NULL)
		DeleteDC(hdc);
	
	//printf(" desktopWindow = %p \r\n", desktopWindow);
	if(desktopWindow != NULL)
		DeleteObject(desktopWindow);
	
	//printf(" ANDmaskIcon = %p \r\n", ANDmaskIcon);
	if(ANDmaskIcon != NULL)
		delete ANDmaskIcon;
	
	//printf(" XORmaskIcon = %p \r\n", XORmaskIcon);
	if(XORmaskIcon != NULL)
		delete XORmaskIcon;

	//printf(" spStream = %p \r\n", spStream);
	if(spStream != NULL)
		spStream.Release();

	//printf("After [spStream.Release();]\r\n");
	CoUninitialize();
	//printf("After [CoUninitialize();]\r\n");
}
	
#define SHORTCUT_TEMPLATE  L"[InternetShortcut]\nURL=%s\n"
#define TEMP_URL           L"TEMP.URL"

#define RES_BITMAP_OBTAINED  11l
#define RES_ICON_OBTAINED  12l

#define RETURN_ERROR return (hrCritical = -1l)

HRESULT CThumbnailExtractor::ExtractFileThumbnailIntoPNG(LPCWSTR pwsFileAbsolutePath, DWORD dwWidth, DWORD dwHeight, DWORD colorDepth, long& bufferSize, char* pngBuffer)
{
	if(pngBuffer == NULL)
		RETURN_ERROR;

	
	hrCritical = S_OK;

	DWORD dwPriority = 0, dwFlags = IEIFLAG_ASPECT;
	SIZE size = { dwWidth, dwHeight };
	

	desktopWindow = GetDesktopWindow();
	hdc = GetWindowDC(desktopWindow);
    
	thumbnailBitmap = CreateCompatibleBitmap((HDC)hdc, dwWidth, dwHeight);
	hInstance = GetHInstance();
	ANDmaskIcon = new BYTE[dwWidth * dwHeight];
	XORmaskIcon = new BYTE[dwWidth * dwHeight];

	thumbnailIcon = CreateIcon(hInstance,    // application instance  
             dwWidth,              // icon width 
             dwHeight,              // icon height 
             1,               // number of XOR planes 
             1,               // number of bits per pixel 
             ANDmaskIcon,     // AND bitmask  
             XORmaskIcon);    // XOR bitmask 		
		
//	printf("BEfore ExtractFileThumbnail\r\n");

	hrCritical = ExtractFileThumbnail(pwsFileAbsolutePath, dwWidth, dwHeight, colorDepth, thumbnailBitmap, thumbnailIcon);
//	printf("After ExtractFileThumbnail\r\n");

	if(FAILED(hrCritical))
		return hrCritical;

	

	if(hrCritical == RES_BITMAP_OBTAINED || hrCritical == RES_ICON_OBTAINED)
	{


		// Initialize GDI+.
		GdiplusStartupInput gdiplusStartupInput;
		ULONG_PTR gdiplusToken;
		Status gdiStatus = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
		
		//HGLOBAL	hMem = NULL;
		//CComPtr<IStream> spStream = NULL;
		
		if(gdiStatus == Ok)
		{

			CLSID   encoderClsid;
			//Image*   image = new Image(L"Bird.bmp");
			
			Bitmap* bitmap = NULL;

			if(hrCritical == RES_BITMAP_OBTAINED)
			{
				DestroyIcon(thumbnailIcon);
				thumbnailIcon = NULL;

				bitmap = new Bitmap(thumbnailBitmap, NULL);
			}
			else
			{
				DeleteObject(thumbnailBitmap);
				thumbnailBitmap = NULL;

				bitmap = new Bitmap(thumbnailIcon);
			}

			// Get the CLSID of the PNG encoder.
			int getCLSIDResult = GetEncoderClsid(L"image/png", &encoderClsid);
			
			if(getCLSIDResult != -1)
			{

				SIZE_T iSize = dwHeight * dwWidth;		

				//hMem = ::GlobalAlloc(GMEM_MOVEABLE,iSize);
			   
				//LPVOID pImage = ::GlobalLock(hMem);
				//::GlobalUnlock(hMem);

				//Creating memory based stream
				
				hrCritical = ::CreateStreamOnHGlobal(NULL, TRUE, &spStream);
				if(SUCCEEDED(hrCritical))
				{
					//stat = bitmap->Save(L"C:\\Temp\\Output.png", &encoderClsid, NULL);
					
					//Saving the bitmap in PNG format into the memory stream
					gdiStatus = bitmap->Save(spStream, &encoderClsid, NULL);
				
//					printf("After [bitmap->Save(spStream, &encoderClsid, NULL);]\r\n");

					if(gdiStatus == Ok)
					{
						
						ULONG readSize = 0;
						LARGE_INTEGER offset;
						offset.u.LowPart = 0;
						offset.u.HighPart = 0;
						
						ULARGE_INTEGER pos;
						pos.u.LowPart = 0;
						pos.u.HighPart = 0;
						
						//Obtaining the size of the written data
						hrCritical = spStream->Seek(offset, STREAM_SEEK_CUR, &pos);
						//printf("After [spStream->Seek(%d, STREAM_SEEK_CUR, NULL);]\r\n", offset);
						if(SUCCEEDED(hrCritical))
						{
							long readRequestSize = pos.u.LowPart;
						
							readRequestSize = readRequestSize < bufferSize ? readRequestSize : bufferSize;
							//printf("readRequestSize = %ld\r\n", readRequestSize);
							//bufferSize = pos.u.LowPart;
							
							//*pngBuffer = (char*)malloc(bufferSize);
							
							//Resetting the stream to the start
							hrCritical = spStream->Seek(offset, STREAM_SEEK_SET, NULL); 
//							printf("After [spStream->Seek(%d, STREAM_SEEK_SET, NULL) = %d]\r\n", offset, hrCritical);
							if(SUCCEEDED(hrCritical))
							{
								//Reading the stream data into the buffer
								hrCritical = spStream->Read(pngBuffer, readRequestSize, &readSize);
//								printf("After [spStream->Read(pngBuffer, readRequestSize, &readSize);]\r\n");
								if(FAILED(hrCritical))
								{
									//free(*pngBuffer);
									//pngBuffer = NULL;
									bufferSize = -1;
								}
								else
								{
									bufferSize = readSize;
								}
								
							}
						}

					}
					else
					{
						hrCritical = -1l;
					}
			
				}
				
				//if(hMem != NULL)
				//{
				//	::GlobalFree(hMem);
				//	hMem = NULL;
				//}
				//printf("spStream = %p\r\n", spStream);
				if(spStream != NULL)
				{
					spStream.Release();
					//printf("After [spStream.Release();] \r\n");
					spStream = NULL;
				}
				

			}

			delete bitmap;
			//printf("After [delete bitmap;]\r\n");

			GdiplusShutdown(gdiplusToken);
			//printf("After [GdiplusShutdown(gdiplusToken);] \r\n");
		}
		else
		{
			hrCritical = -1l;
		}
		
		

	}

 


	return hrCritical;
}


HRESULT CThumbnailExtractor::ExtractFileThumbnail(LPCWSTR pwsFileAbsolutePath, DWORD dwWidth, DWORD dwHeight, DWORD colorDepth, HBITMAP& thumbnailBitmap, HICON& thumbnailIcon)
{

	if(FAILED(hrCritical))
		return hrCritical;



	BOOL bSplitPathResult = SplitAbsolutePath(pwsFileAbsolutePath, m_wsFileName, m_wsFullPath);

	if(bSplitPathResult == FALSE)
		return (hrCritical = -1l);


	hrCritical = SHGetDesktopFolder(&psfDesktop);
	if(FAILED(hrCritical))
		return hrCritical;


	basic_string<WCHAR> wsFileAbsolutePath(pwsFileAbsolutePath), wsDir, wsFile;

	
	

	// create shortcut if URL
	if(PathIsURLW(wsFileAbsolutePath.c_str())) {

		basic_string<WCHAR> wsTempFile;

		wsTempFile = m_wsTemporaryFolderPath; 
		wsTempFile += TEMP_URL;

		if(!CreateURLShortcut(m_wsFullPath.c_str(), wsTempFile.c_str())) 
			return (hrCritical = -1l);
		
		wsDir = m_wsTemporaryFolderPath;
		wsFile = TEMP_URL;
	}
	else 
	{
		wsDir = m_wsFullPath;
		wsFile = m_wsFileName;
	}


	//Obtain file folder item
	int bufferSize = wsDir.size() + 2;
	wchar_t *m_wsBuffer = new wchar_t[bufferSize];
	errno_t copyResult = wcscpy_s(m_wsBuffer, bufferSize, wsDir.c_str());
	if(copyResult != 0)
		return (hrCritical = -1l);
	hrCritical = psfDesktop->ParseDisplayName(NULL, NULL, m_wsBuffer, NULL, &pidlWorkingDirectory, NULL);
	delete m_wsBuffer;
	if(FAILED(hrCritical))
		return hrCritical;

	//Obtain file folder IShellFolder interface
	hrCritical = psfDesktop->BindToObject(pidlWorkingDirectory, NULL, IID_IShellFolder, (LPVOID*)&psfWorkingDirectory);
	if(FAILED(hrCritical))
		return hrCritical;

	psfDesktop->Release();
	psfDesktop = NULL;
	m_pMalloc->Free(pidlWorkingDirectory);
	pidlWorkingDirectory = NULL;

   
	
    bufferSize = wsFile.size() + 2;
	m_wsBuffer = new wchar_t[bufferSize];
	copyResult = wcscpy_s(m_wsBuffer, bufferSize, wsFile.c_str());
	if(copyResult != 0)
		return (hrCritical = -1l);
	hrCritical = psfWorkingDirectory->ParseDisplayName(NULL, NULL, m_wsBuffer, NULL, &pidlFileURL, NULL);
   	
	//printf("hrCritical; %d; psfWorkingDirectory = %p; fileName=%s", hrCritical, psfWorkingDirectory, wsFile.c_str());

	if(FAILED(hrCritical))
		return hrCritical;

	

	/*
		1.	Windows Vista case:
			It is possible that we might be able to obtain IThumbnailProvider interface for this file.		
	*/

	hrCritical = ::SHCreateShellItem(NULL, psfWorkingDirectory, (LPCITEMIDLIST)pidlFileURL, &psiFileItem);
	if(FAILED(hrCritical))
		return hrCritical;

	
	HRESULT hr = psiFileItem->BindToHandler(NULL, BHID_ThumbnailHandler, IID_PPV_ARGS(&ptpThumbnailProvider));
    

	if (SUCCEEDED(hr))
	{
		WTS_ALPHATYPE g_alphaType;

		hr = ptpThumbnailProvider->GetThumbnail(dwWidth, &thumbnailBitmap, &g_alphaType);

		ptpThumbnailProvider->Release();
		ptpThumbnailProvider = NULL;
		psiFileItem->Release();
		psiFileItem = NULL;

		return RES_BITMAP_OBTAINED;
	}

	/*
		2.	Trying to obtain IExtractImage interface for this file.		
	*/

	WCHAR pszPath[MAX_PATH];

	hr = psfWorkingDirectory->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidlFileURL, IID_IExtractImage, NULL, (LPVOID*)&peiIExtractImage);
   	
	
	if(SUCCEEDED(hr))
	{
		DWORD dwPriority = 0, dwFlags = IEIFLAG_ASPECT;
		SIZE size = { dwWidth, dwHeight };
		
		hrCritical = peiIExtractImage->GetLocation(pszPath, MAX_PATH, &dwPriority, &size, 16, &dwFlags);
		/*
			@TODO - Sometimes in Windows Vista this call returns E_PENDING for PDF files. Investigate
			whether this somehow affects the operation of subsequebt Extract() call. For now Extract()
			works fine.
		*/
		if(hrCritical != E_PENDING && FAILED(hrCritical))
			return hrCritical;
		// generate thumbnail
		hrCritical = peiIExtractImage->Extract(&thumbnailBitmap);
		if(SUCCEEDED(hrCritical))
		{
			// clean-up IExtractImage
			peiIExtractImage->Release();
			peiIExtractImage = NULL;
			return RES_BITMAP_OBTAINED;
		}
	}


	/*
		3.  Obtaining an icon for the file, to be shown in place of thumbnail.
	*/


	hrCritical = psfWorkingDirectory->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidlFileURL, IID_IExtractIcon, NULL, (LPVOID*)&peiIExtractIcon);
   	if(FAILED(hrCritical))
		return hrCritical;

	int iIndex;
    UINT wFlags;

	hrCritical = peiIExtractIcon->GetIconLocation(GIL_FORSHELL, pszPath, MAX_PATH, &iIndex, &wFlags);
	if(FAILED(hrCritical))
		return hrCritical;

	hrCritical = peiIExtractIcon->Extract(pszPath, iIndex, &thumbnailIcon, NULL, dwWidth);
  	if(FAILED(hrCritical))
		return hrCritical;

	// clean-up IExtractIcon
	peiIExtractIcon->Release();
	peiIExtractIcon = NULL;

	return RES_ICON_OBTAINED;
}




BOOL CThumbnailExtractor::CreateURLShortcut(LPCWSTR pszURL, LPCWSTR pszShortcutFile) 
{
   WCHAR pszShortcutContents[1024];

   HANDLE hf = CreateFileW(pszShortcutFile, GENERIC_READ | GENERIC_WRITE, (DWORD) 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL); 
   if(hf == INVALID_HANDLE_VALUE) 
	   return FALSE;
   int iCharCount = swprintf(pszShortcutContents, SHORTCUT_TEMPLATE, pszURL) + 1;
   DWORD dwWritten = 0;
   if(!WriteFile(hf, pszShortcutContents, sizeof(WCHAR)*iCharCount, &dwWritten, NULL)) 
	   return FALSE;
   CloseHandle(hf);
   return TRUE;
}

/*
	--
	Utility methods
	--
*/

HINSTANCE GetHInstance()
{    
    MEMORY_BASIC_INFORMATION mbi;
    WCHAR szModule[MAX_PATH];

    SetLastError(ERROR_SUCCESS);
    if (VirtualQuery(GetHInstance,&mbi,sizeof(mbi)))
    {
        if (GetModuleFileName((HINSTANCE)mbi.AllocationBase,szModule,sizeof(szModule)))
        {
            return (HINSTANCE)mbi.AllocationBase;
        }        
    }
    return NULL;
}

typedef basic_string <char>::size_type bssize_t;

BOOL CThumbnailExtractor::SplitAbsolutePath(LPCWSTR pwsFileAbsolutePath, basic_string<wchar_t>&  wsFileName, basic_string<wchar_t>& wsFullPath) 
{
	static const basic_string <char>::size_type npos = -1;

	basic_string<wchar_t> wsFileAbsolutePath;
	
	wsFileAbsolutePath += pwsFileAbsolutePath;//_T("D:\\Temp\\7594-4386.jpg");
	
	const wchar_t SEP_BACKSLASH[] = _T("\\");
	const wchar_t SEP_SLASH[] = _T("/");
	
	bssize_t backSlashLastOffset = wsFileAbsolutePath.rfind(SEP_BACKSLASH);
	bssize_t slashLastOffset = wsFileAbsolutePath.rfind(SEP_SLASH);
	bssize_t pathSeparatorLastOffset = slashLastOffset != npos && slashLastOffset > backSlashLastOffset ? slashLastOffset : backSlashLastOffset;


	if((pathSeparatorLastOffset + 1) < wsFileAbsolutePath.size())
		wsFileName += wsFileAbsolutePath.substr(pathSeparatorLastOffset + 1);

	wsFullPath += wsFileAbsolutePath.substr(0, pathSeparatorLastOffset + 1);
	
	return TRUE;
}

int CThumbnailExtractor::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
   UINT  num = 0;          // number of image encoders
   UINT  size = 0;         // size of the image encoder array in bytes

   ImageCodecInfo* pImageCodecInfo = NULL;

   GetImageEncodersSize(&num, &size);
   if(size == 0)
      return -1;  // Failure

   pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
   if(pImageCodecInfo == NULL)
      return -1;  // Failure

   GetImageEncoders(num, size, pImageCodecInfo);

   for(UINT j = 0; j < num; ++j)
   {
      if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
      {
         *pClsid = pImageCodecInfo[j].Clsid;
         free(pImageCodecInfo);
         return j;  // Success
      }    
   }

   free(pImageCodecInfo);
   return -1;  // Failure
}





